/*
 *  Checkpoint/restart - dump state of sysvipc sem
 *
 *  Copyright (C) 2009 Oren Laadan
 *
 *  This file is subject to the terms and conditions of the GNU General Public
 *  License.  See the file COPYING in the main directory of the Linux
 *  distribution for more details.
 */

/* default debug level for output */
#define CKPT_DFLAG  CKPT_DIPC

#include <linux/mm.h>
#include <linux/sem.h>
#include <linux/rwsem.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/nsproxy.h>
#include <linux/ipc_namespace.h>

struct msg_msg;
#include "util.h"

#include <linux/checkpoint.h>

/************************************************************************
 * ipc checkpoint
 */

/* called with the msgids->rw_mutex is read-held */
static int fill_ipc_sem_hdr(struct ckpt_ctx *ctx,
			       struct ckpt_hdr_ipc_sem *h,
			       struct sem_array *sem)
{
	int ret = 0;

	ret = checkpoint_fill_ipc_perms(ctx, &h->perms, &sem->sem_perm);
	if (ret < 0)
		return ret;

	ipc_lock_by_ptr(&sem->sem_perm);
	h->sem_otime = sem->sem_otime;
	h->sem_ctime = sem->sem_ctime;
	h->sem_nsems = sem->sem_nsems;
	ipc_unlock(&sem->sem_perm);

	ckpt_debug("sem: nsems %u\n", h->sem_nsems);

	return 0;
}

/**
 * ckpt_write_sem_array - dump the state of a semaphore array
 * @ctx: checkpoint context
 * @sem: semphore array
 *
 * The state of a sempahore is an array of 'struct sem'. This structure
 * is {int, int}, which translates to the same format {32 bits, 32 bits}
 * on both 32- and 64-bit architectures. So we simply dump the array.
 *
 * The sem-undo information is not saved per ipc_ns, but rather per task.
 */
static int checkpoint_sem_array(struct ckpt_ctx *ctx, struct sem_array *sem)
{
	/* this is a "best-effort" test, so lock not needed */
	if (!list_empty(&sem->sem_pending))
		return -EBUSY;

	/* our caller holds the mutex, so this is safe */
	return ckpt_write_buffer(ctx, sem->sem_base,
			       sem->sem_nsems * sizeof(*sem->sem_base));
}

/* called with the msgids->rw_mutex is read-held */
int checkpoint_ipc_sem(int id, void *p, void *data)
{
	struct ckpt_hdr_ipc_sem *h;
	struct ckpt_ctx *ctx = (struct ckpt_ctx *) data;
	struct kern_ipc_perm *perm = (struct kern_ipc_perm *) p;
	struct sem_array *sem;
	int ret;

	sem = container_of(perm, struct sem_array, sem_perm);

	h = ckpt_hdr_get_type(ctx, sizeof(*h), CKPT_HDR_IPC_SEM);
	if (!h)
		return -ENOMEM;

	ret = fill_ipc_sem_hdr(ctx, h, sem);
	if (ret < 0)
		goto out;

	ret = ckpt_write_obj(ctx, &h->h);
	if (ret < 0)
		goto out;

	if (h->sem_nsems)
		ret = checkpoint_sem_array(ctx, sem);
 out:
	ckpt_hdr_put(ctx, h);
	return ret;
}

/************************************************************************
 * ipc restart
 */

/* called with the msgids->rw_mutex is write-held */
static int load_ipc_sem_hdr(struct ckpt_ctx *ctx,
			       struct ckpt_hdr_ipc_sem *h,
			       struct sem_array *sem)
{
	int ret = 0;

	ret = restore_load_ipc_perms(ctx, &h->perms, &sem->sem_perm);
	if (ret < 0)
		return ret;

	ckpt_debug("sem: nsems %u\n", h->sem_nsems);

	sem->sem_otime = h->sem_otime;
	sem->sem_ctime = h->sem_ctime;
	sem->sem_nsems = h->sem_nsems;

	return 0;
}

/**
 * ckpt_read_sem_array - read the state of a semaphore array
 * @ctx: checkpoint context
 * @sem: semphore array
 *
 * Expect the data in an array of 'struct sem': {32 bit, 32 bit}.
 * See comment in ckpt_write_sem_array().
 *
 * The sem-undo information is not restored per ipc_ns, but rather per task.
 */
static struct sem *restore_sem_array(struct ckpt_ctx *ctx, int nsems)
{
	struct sem *sma;
	int i, ret;

	sma = kmalloc(nsems * sizeof(*sma), GFP_KERNEL);
	if (!sma)
		return ERR_PTR(-ENOMEM);
	ret = _ckpt_read_buffer(ctx, sma, nsems * sizeof(*sma));
	if (ret < 0)
		goto out;

	/* validate sem array contents */
	for (i = 0; i < nsems; i++) {
		if (sma[i].semval < 0 || sma[i].sempid < 0) {
			ret = -EINVAL;
			break;
		}
	}
 out:
	if (ret < 0) {
		kfree(sma);
		sma = ERR_PTR(ret);
	}
	return sma;
}

int restore_ipc_sem(struct ckpt_ctx *ctx, struct ipc_namespace *ns)
{
	struct ckpt_hdr_ipc_sem *h;
	struct kern_ipc_perm *ipc;
	struct sem_array *sem;
	struct sem *sma = NULL;
	struct ipc_ids *sem_ids = &ns->ids[IPC_SEM_IDS];
	int semflag, ret, i;

	h = ckpt_read_obj_type(ctx, sizeof(*h), CKPT_HDR_IPC_SEM);
	if (IS_ERR(h))
		return PTR_ERR(h);

	ret = -EINVAL;
	if (h->perms.id < 0)
		goto out;
	if (h->sem_nsems < 0)
		goto out;

	/* read sempahore array state */
	sma = restore_sem_array(ctx, h->sem_nsems);
	if (IS_ERR(sma)) {
		ret = PTR_ERR(sma);
		sma = NULL;
		goto out;
	}

	/* restore the message queue now */
	semflag = h->perms.mode | IPC_CREAT | IPC_EXCL;
	ckpt_debug("sem: do_semget key %d flag %#x id %d\n",
		 h->perms.key, semflag, h->perms.id);
	ret = do_semget(ns, h->perms.key, h->sem_nsems, semflag, h->perms.id);
	ckpt_debug("sem: do_semget ret %d\n", ret);
	if (ret < 0)
		goto out;

	down_write(&sem_ids->rw_mutex);

	/*
	 * We are the sole owners/users of this brand new ipc-ns, so:
	 *
	 * 1) The semid could not have been deleted between its creation
	 *   and taking the rw_mutex above.
	 * 2) No unauthorized task will attempt to gain access to it,
	 *   so it is safe to do away with ipc_lock(). This is useful
	 *   because we can call functions that sleep.
	 * 3) Likewise, we only restore the security bits further below,
	 *   so it is safe to ignore this (theoretical only!) race.
	 *
	 * If/when we allow to restore the ipc state within the parent's
	 * ipc-ns, we will need to re-examine this.
	 */
	ipc = ipc_lock(sem_ids, h->perms.id);
	BUG_ON(IS_ERR(ipc));

	sem = container_of(ipc, struct sem_array, sem_perm);
	memcpy(sem->sem_base, sma, sem->sem_nsems * sizeof(*sma));

	for (i = 0; i < sem->sem_nsems; i++)
		INIT_LIST_HEAD(&sem->sem_base[i].sem_pending);

	/* this is safe because no unauthorized access is possible */
	ipc_unlock(ipc);

	ret = load_ipc_sem_hdr(ctx, h, sem);
	if (ret < 0) {
		ipc_lock_by_ptr(&sem->sem_perm);
		ckpt_debug("sem: need to remove (%d)\n", ret);
		freeary(ns, ipc);
		ipc_unlock(ipc);
	}
	up_write(&sem_ids->rw_mutex);
 out:
	kfree(sma);
	ckpt_hdr_put(ctx, h);
	return ret;
}
